#ifndef _OCPP_Grid3dData_H__
#define _OCPP_Grid3dData_H__

#include <emscripten.h>
#include <emscripten/bind.h>

#include "OCesium/Cartesian2.h"
#include "OCesium/Matrix3.h"
#include "OCesium/TilingScheme.h"
#include <cfloat>

#include "immintrin.h"
#include "fp16/fp16.h"

using namespace OCPP;

inline Cesium::Rectangle js_getRectangle(const emscripten::val &jsRectangle)
{
    return Cesium::Rectangle::fromRadians(
        jsRectangle["west"].as<float>(),
        jsRectangle["south"].as<float>(),
        jsRectangle["east"].as<float>(),
        jsRectangle["north"].as<float>());
}

inline float float16ToFloat32(uint16_t float16Value)
{
    return fp16_ieee_to_fp32_value(float16Value);
}

class Grid3dData
{
public:
    Grid3dData(const emscripten::val &jsImage)
    {
        rectangle = Cesium::Rectangle::fromDegrees(
            jsImage["west"].as<float>(),
            jsImage["south"].as<float>(),
            jsImage["east"].as<float>(),
            jsImage["north"].as<float>());

        const emscripten::val &source = jsImage["source"];

        width = source["width"].as<int>();
        height = source["height"].as<int>();
        if (!(source["depth"].isUndefined() || source["depth"].isNull()))
        {
            depth = source["depth"].as<int>();
        }

        dx = rectangle.getWidth() / (width - 1.0);
        dy = rectangle.getHeight() / (height - 1.0);

        size_t dataSize = width * height * depth;

        const emscripten::val &arrayBufferView = source["arrayBufferView"];
        if (arrayBufferView.isUndefined() || arrayBufferView.isNull())
            return;
        int length = arrayBufferView["length"].as<int>();
        comp = length / dataSize;
        float minValue = FLT_MAX;
        float maxValue = FLT_MIN;

        int pixelDatatype = source["pixelDatatype"].as<int>();
        isTypeByte = pixelDatatype == 5121;
        isTypeFloat = pixelDatatype == 5126;
        isTypeHalfFloat = pixelDatatype == 36193;
        if (isTypeByte)
        {
            vecCharData.resize(length);
            emscripten::val view(emscripten::typed_memory_view(
                vecCharData.size(),
                vecCharData.data()));
            view.call<void>("set", arrayBufferView);
        }
        else if (isTypeHalfFloat)
        {
            std::vector<uint16_t> vecShortData;
            vecShortData.resize(length);
            emscripten::val view(emscripten::typed_memory_view(
                vecShortData.size(),
                vecShortData.data()));
            view.call<void>("set", arrayBufferView);

            vecFloatData.resize(length);
            for (auto i = 0; i < vecShortData.size(); i++)
            {
                vecFloatData[i] = float16ToFloat32(vecShortData[i]) * scale + offset;
            }

            isTypeFloat = true;
            isTypeHalfFloat = false;
        }
        else
        {
            vecFloatData.resize(length);
            emscripten::val view(emscripten::typed_memory_view(
                vecFloatData.size(),
                vecFloatData.data()));
            view.call<void>("set", arrayBufferView);
        }
    }

    String toString()
    {
        StringStream sstream;
        sstream << "width:" << width << "\t";
        sstream << "height:" << height << "\t";
        sstream << "depth:" << depth << "\t";
        sstream << "rectangle:" << rectangle.toString();
        return sstream.str();
    }

    Cesium::Cartesian3 getUV(size_t c, size_t r, size_t z)
    {
        Cesium::Cartesian3 uvw;

        if (flipY)
        {
            r = height - 1 - r;
        }

        size_t dimYX = width * height;
        size_t i = z * dimYX + r * width + c;

        if (isTypeByte)
        {
            uint8_t u0 = vecCharData[i * comp];
            uint8_t u1 = vecCharData[i * comp + 1];
            uint8_t v0 = vecCharData[i * comp + 2];
            uint8_t v1 = vecCharData[i * comp + 3];
            uvw.x = floor(u0 - 127) + u1 / 255.0;
            uvw.y = floor(v0 - 127) + v1 / 255.0;
        }
        else if (isTypeFloat)
        {
            uvw.x = vecFloatData[i * comp];
            uvw.y = vecFloatData[i * comp + 1];
        }
        else if (isTypeFloat)
        {
            uvw.x = vecFloatData[i * comp];
            uvw.y = vecFloatData[i * comp + 1];
        }

        return uvw;
    }

    bool isTypeByte;
    bool isTypeFloat;
    bool isTypeHalfFloat;

    bool flipY = false;
    size_t comp;
    std::vector<uint8_t> vecCharData;
    std::vector<float> vecFloatData;
    Cesium::Rectangle rectangle;
    int width, height, depth = 1;

    float dx, dy;

    float scale = 1.0;
    float offset = 0.0;
};

#endif